package model.algorithms;

import java.util.ArrayList;
import java.util.List;

import model.config.Config;
import model.framework.Frame;
import model.framework.Block;
import model.framework.BlockType;
import model.framework.Field;

public abstract class Algorithm {
	
	/**
	 * Returns the blocks after the given block.
	 * @param block the given block from which the following blocks is needed  
	 * @param nrOfLookAheads number of following blocks
	 * @return
	 */
	protected List<Block> getLookAheadBlocks(BlockType block, int nrOfLookAheads, List<Block> blocks) {
		List<Block> lookAheadBlocks = new ArrayList<Block>();
		
		int blockIndex = blocks.indexOf(block);
		
		if((blockIndex + 1) != blocks.size()) {
			int beginIndex = blockIndex + 1;
			int endIndex = 0;
			
			if((beginIndex + nrOfLookAheads) < blocks.size()) {
				endIndex = beginIndex + nrOfLookAheads;
			} else {
				endIndex = blocks.size() - 1;
			}
			
			for(;beginIndex <= endIndex; beginIndex++) {
				lookAheadBlocks.add(blocks.get(beginIndex));
			}
		}
		
		return lookAheadBlocks;
	}
	
	/**
	 * @param blockType
	 * @param fieldSurface
	 * @return builds 
	 */
	protected List<Frame> buildFrames( BlockType blockType, int[] fieldSurface, int iRotation ) {
		
		List<Frame> frames = new ArrayList<Frame>();
		
			 int blockWidth = blockType.getWidth( iRotation );
			 
			 /* go through all possible bin positions */
			 for( int iFieldx=0; iFieldx <= fieldSurface.length - blockWidth; iFieldx++ ){
				 int[] binDimension = new int[ blockWidth ];
				 
				 /* loop through the binwidth */
				 for( int iBinWidth=0; iBinWidth < binDimension.length; iBinWidth++ ){
					 binDimension[iBinWidth] = fieldSurface[iFieldx + iBinWidth];   
				 }
				 
				 /* create a bin with the dimensions and the position of the bin within the field */
				 Frame frame = new Frame( binDimension, iFieldx, this.calculateHighestSurface( fieldSurface ) );
				 frames.add( frame );			 
			 }
		//}
		
		return frames;
	}
	
	protected int calculateHighestSurface( int[] surface ){
		return this.calculateHighestSurface( surface, 0, surface.length );
	}
	
	/**
	 * Finds the highest surface height in the given surface
	 * @param surface
	 * @return highest value from surface
	 */
	protected int calculateHighestSurface( int[] surface, int start, int end ) {
		/* We have to start somewhere */
		int highest = 0;
		
		/* Find the highest surface value */
		for( int index = start; index < end; index++ ) {
			if( surface[ index ] > highest ) {
				highest = surface[ index ];
			}
		}
		
		/* There ya go */
		return highest;
	}
	
	protected int calculateLowestSurface( int[] surface ) {
		
		return this.calculateLowestSurface( surface, 0, surface.length );
		
	}
	
	/**
	 * Finds the lowest surface height in the given surface
	 * @param surface
	 * @return lowest value from surface
	 */
	protected int calculateLowestSurface( int[] surface, int start, int end ) {
		
		/* We have to start somewhere */
		int lowest = surface[ start ];
		
		/* Find the lowest surface value */
		for( int index = start+1; index < end; index++ ) {
			if( surface[ index ] < lowest ) {
				lowest = surface[ index ];
			}
		}
		
		/* There ya go */
		return lowest;
	}
	
	/*
	 * Returns the height that has to be added to y to be able to place the block 
	 */
	protected int spaceBelowBlockForPlacing( int[] frameSurface, int[] surfaceBelowBlock ){

		for( int iFrameSurface=0; iFrameSurface < frameSurface.length; iFrameSurface++ ){
			frameSurface[ iFrameSurface ] = frameSurface[ iFrameSurface ] + surfaceBelowBlock[ iFrameSurface ];
		}
		
		return calculateLowestSurface( frameSurface );
	}

	public abstract void run(List<Block> blocks, Field field);
}
